package com.alcohol.auth.server.support.grant.base;

import com.alcohol.auth.server.constant.AuthGrantType;
import com.alcohol.auth.server.utils.HttpServletUtils;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 基础认证转换器抽象类
 * @author LiXinYu
 * @date 2025/11/8
 */
public abstract class BaseAuthenticationConverter<T extends BaseAuthenticationToken> implements AuthenticationConverter {

    /**
     * 将HttpServletRequest转换为Authentication对象
     * @param request HTTP请求对象
     * @return 认证信息对象
     * @throws OAuth2AuthenticationException 当请求参数不合法或客户端未认证时抛出异常
     */
    @Override
    public Authentication convert(HttpServletRequest request) {
        // 判断自定义授权类型是否包含grant_type
        String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
        if (!Arrays.stream(AuthGrantType.values()).map(AuthGrantType::getGrantType).toList().contains(grantType)) {
            return null;
        }

        // 校验http媒体类型
        if (!MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType())) {
            throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, "不支持的媒体类型！", null));
        }

        // 获取请求入参
        MultiValueMap<String, String> parameters = HttpServletUtils.getParameters(request);
        // scope (OPTIONAL)
        String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
        if (StringUtils.hasText(scope) && CollectionUtils.isEmpty(parameters.get(OAuth2ParameterNames.SCOPE))) {
            throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE, "scope必填！", null));
        }

        // 保存请求的认证作用域
        Set<String> requestedScopes = null;
        if (StringUtils.hasText(scope)) {
            requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
        }

        // 校验定制化参数
        this.checkParams(parameters);

        // 获取当前已经认证的客户端信息
        Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
        if (clientPrincipal == null) {
            throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, "客户端失败！", null));
        }

        // 附加信息
        Map<String, Object> additionalParameters = this.getAdditionalParameters(parameters);

        // 创建token
        return this.buildToken(parameters, clientPrincipal, requestedScopes, additionalParameters);
    }

    /**
     * 校验参数
     * @param parameters 请求入参
     */
    public abstract void checkParams(MultiValueMap<String, String> parameters);

    /**
     * 构建具体类型的token
     * @param parameters 请求入参
     * @param clientPrincipal 客户端认证信息
     * @param requestedScopes 请求的认证作用域
     * @param additionalParameters 扩展信息
     * @return 认证令牌
     */
    public abstract T buildToken(MultiValueMap<String, String> parameters, Authentication clientPrincipal, Set<String> requestedScopes, Map<String, Object> additionalParameters);

    /**
     * 获取附加信息
     * @param parameters 请求入参
     * @return 附加信息
     */
    public abstract Map<String, Object> getAdditionalParameters(MultiValueMap<String, String> parameters);
}
